/**
 * minizinccodegenerator.cpp is part of %PROJECTNAME%.
 *
 * %PROJECTNAME% is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * %PROJECTNAME% is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with %PROJECTNAME%.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "minizinccodegenerator.h"
#include "../model.h"
#include "../task.h"
#include "../resource.h"
#include "../constraints/tasktimewindowconstraint.h"
#include "../constraints/tasklenghtconstraint.h"
#include "../constraints/taskresourcesneededconstraint.h"
#include "../constraints/resourcecapacityconstraint.h"
#include "../constraints/modelallowidlesconstraint.h"
#include "../constraints/modelmaxtimeconstraint.h"
#include "../constraints/modelprecedenceconstraint.h"

const std::string MiniZincCodeGenerator::N_TASKS            = "n_tasks";
const std::string MiniZincCodeGenerator::N_RESOURCES        = "n_resources";
const std::string MiniZincCodeGenerator::CAPACITY           = "capacity";
const std::string MiniZincCodeGenerator::RESOURCES          = "resources";
const std::string MiniZincCodeGenerator::LENGHT             = "lenght";
const std::string MiniZincCodeGenerator::MAX_TIME_STR       = "max_time";
const std::string MiniZincCodeGenerator::START_TIME_WINDOW  = "start_time_window";
const std::string MiniZincCodeGenerator::END_TIME_WINDOW    = "end_time_window";
const std::string MiniZincCodeGenerator::HEADER             = "% Autogenerated minizinc data file.";
const std::string MiniZincCodeGenerator::ALLOW_IDLES        = "allow_idle";
const std::string MiniZincCodeGenerator::TASK_NAME          = "taskName";
const std::string MiniZincCodeGenerator::RESOURCE_NAME      = "resourceName";
const std::string MiniZincCodeGenerator::DO_PRECEDENCES     = "do_precedences";
const std::string MiniZincCodeGenerator::N_PRECEDENCES      = "n_precedences";
const std::string MiniZincCodeGenerator::ARRAY_PRECEDENCES  = "precedences = array2d(1..n_precedences, 1..2,";
const std::string MiniZincCodeGenerator::DUMMY_MATRIX       = "[ 0,0 ]);";

MiniZincCodeGenerator::MiniZincCodeGenerator() :
    Visitor(),
    m_strStream(),
    m_maxTime( MAX_TIME ),
    m_nTasks( 0 ),
    m_nResources( 0 ),
    m_lenghts(),
    m_resources(),
    m_capacities(),
    m_start_times_windows(),
    m_end_times_windows(),
    m_modelName( "" ),
    m_allowIdle( false ),
    m_tasksNames(),
    m_resourcesNames(),
    m_precedences()
{
}

MiniZincCodeGenerator::~MiniZincCodeGenerator()
{
    Reset();
}

void MiniZincCodeGenerator::Visit( Model * a_model )
{
    if( a_model )
    {
        m_nTasks     = a_model->NTasks();
        m_nResources = a_model->NResources();
        m_modelName  = a_model->ModelName();
    }
}

void MiniZincCodeGenerator::Visit( TaskTimeWindowConstraint * a_constraint )
{
    if( a_constraint )
    {
        m_start_times_windows.push_back( a_constraint->Start() );
        m_end_times_windows.push_back( a_constraint->End() );
    }
}

void MiniZincCodeGenerator::Visit( TaskLenghtConstraint * a_constraint )
{
    if( a_constraint )
    {
        m_lenghts.push_back( a_constraint->Lenght() );
    }
}

void MiniZincCodeGenerator::Visit( TaskResourcesNeededConstraint * a_constraint )
{
    if( a_constraint )
        m_resources.push_back( a_constraint->NeededResources() );
}

void MiniZincCodeGenerator::Visit( ResourceCapacityConstraint * a_constraint )
{
    if( a_constraint )
    {
        m_capacities.push_back( a_constraint->Capacity() );
    }
}

void MiniZincCodeGenerator::Visit( ModelAllowIdlesConstraint * a_constraint )
{
    if( a_constraint )
        m_allowIdle = a_constraint->AllowIdles();
}

void MiniZincCodeGenerator::Visit( ModelMaxTimeConstraint * a_constraint )
{
    if( a_constraint )
        m_maxTime = a_constraint->MaxTime();
}

void MiniZincCodeGenerator::Visit( ModelPrecedenceConstraint * a_constraint )
{
    if( a_constraint )
    {
        std::size_t taskBefore = Idx( m_tasksNames, a_constraint->TaskBefore() );
        std::size_t taskAfter  = Idx( m_tasksNames, a_constraint->TaskAfter() );
        m_precedences.push_back( std::make_pair( taskBefore, taskAfter  ) );
    }
}

std::size_t MiniZincCodeGenerator::Idx( const tStringVector & a_vector, const std::string & a_str )
{
    std::size_t i    = 0;
    std::size_t iEnd = a_vector.size();
    bool found = false;
    while( !found && i < iEnd )
    {
        found = a_vector[ i ] == a_str;
        ++i;
    }

    return found ? i-1 : iEnd;
}

void MiniZincCodeGenerator::Visit( Task * a_task )
{
    if( a_task )
        m_tasksNames.push_back( a_task->TaskName() );
}

void MiniZincCodeGenerator::Visit( Resource * a_resource )
{
    if( a_resource )
        m_resourcesNames.push_back( a_resource->ResourceName() );
}

std::string MiniZincCodeGenerator::Print() const
{
    std::ostringstream strStream;
    strStream << HEADER << "Model " << m_modelName << std::endl;
    strStream << MAX_TIME_STR << " = " << m_maxTime << ";" << std::endl;
    std::string allowIdleStr = m_allowIdle ? "true" : "false";
    strStream << ALLOW_IDLES << " = " << allowIdleStr << ";" << std::endl;
    strStream << N_TASKS << " = " << m_nTasks << ";" << std::endl;
    strStream << TASK_NAME << " = " << PrintVector< std::string >( m_tasksNames ) << ";" << std::endl;
    strStream << N_RESOURCES << " = " << m_nResources << ";" << std::endl;
    strStream << RESOURCE_NAME << " = " << PrintVector< std::string >( m_resourcesNames ) << ";" << std::endl;
    strStream << PrintLenght() << ";" << std::endl;
    strStream << PrintStartTimeWindow() << ";" << std::endl;
    strStream << PrintEndTimeWindow() << ";" << std::endl;
    strStream << PrintResources() << ";" << std::endl;
    strStream << PrintCapacity() << ";" << std::endl;
    strStream << PrintVector< tPrecedence >( m_precedences ) << std::endl;
    return strStream.str();
}

void MiniZincCodeGenerator::Reset()
{
    m_strStream.flush();
    m_nTasks = 0;
    m_nResources = 0;
    m_lenghts.clear();
    m_resources.clear();
    m_capacities.clear();
    m_start_times_windows.clear();
    m_end_times_windows.clear();
    m_maxTime = MAX_TIME;
    m_allowIdle = false;
    m_tasksNames.clear();
    m_resourcesNames.clear();
    m_precedences.clear();
}

std::string MiniZincCodeGenerator::PrintLenght() const
{
    std::ostringstream strStream;
    strStream << LENGHT << " = " << PrintVector< tTime >( m_lenghts );

    return strStream.str();
}

std::string MiniZincCodeGenerator::PrintStartTimeWindow() const
{
    std::ostringstream strStream;
    strStream << START_TIME_WINDOW << " = " << PrintVector< tTime >( m_start_times_windows ) ;

    return strStream.str();
}

std::string MiniZincCodeGenerator::PrintEndTimeWindow() const
{
    std::ostringstream strStream;
    strStream << END_TIME_WINDOW << " = " << PrintVector< tTime >( m_end_times_windows ) ;

    return strStream.str();
}

std::string MiniZincCodeGenerator::PrintResources() const
{
    std::ostringstream strStream;
    strStream << RESOURCES << " = " << PrintVector< tNeededResources >( m_resources );

    return strStream.str();
}

std::string MiniZincCodeGenerator::PrintCapacity() const
{
    std::ostringstream strStream;
    strStream << CAPACITY << " = " << PrintVector< tCapacity >( m_capacities );

    return strStream.str();
}

template<>
std::string MiniZincCodeGenerator::PrintVector( const std::vector< std::string > & a_vector ) const
{
    std::ostringstream strStream;

    tStringVector::const_iterator itBegin = a_vector.begin();
    tStringVector::const_iterator it      = a_vector.begin();
    tStringVector::const_iterator itEnd   = a_vector.end();
    while( it != itEnd )
    {
        if( it == itBegin )
            strStream << "[";

        strStream << "\"" << *it << "\"";
        ++it;

        if( it != itEnd )
            strStream << ",";
        else
            strStream << "]";
    }

    return strStream.str();
}

template<>
std::string MiniZincCodeGenerator::PrintVector( const tPrecedencesVector & a_vector ) const
{
    std::ostringstream strStream;
    bool noPrecedences = a_vector.empty();
    std::string doPrecedencesStr = noPrecedences ? "false" : "true";
    std::size_t nPrecedences = noPrecedences ? 1 : a_vector.size();
    strStream << DO_PRECEDENCES << " = " << doPrecedencesStr  << ";" << std::endl;
    strStream << N_PRECEDENCES  << " = " << nPrecedences      << ";" << std::endl;
    strStream << ARRAY_PRECEDENCES << std::endl;

    if( a_vector.empty() )
    {
        strStream << DUMMY_MATRIX << std::endl;
    }
    else
    {
        tPrecedencesVector::const_iterator itBegin = m_precedences.begin();
        tPrecedencesVector::const_iterator it      = m_precedences.begin();
        tPrecedencesVector::const_iterator itEnd   = m_precedences.end();
        while( it != itEnd )
        {
            if( it == itBegin )
                strStream << "[" << std::endl;

            strStream << (*it).first + 1 << "," << (*it).second + 1;

            ++it;

            if( it != itEnd )
                strStream << "," << std::endl;
            else
                strStream << std::endl << "]);" << std::endl;
        }
    }
    
    return strStream.str();
}
